home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1997 September / Macworld (1997-09).dmg / Shareware World / Utilities / Text Processing / Alpha / Tcl / Modes / bibtex.tcl < prev    next >
Text File  |  1996-09-25  |  44KB  |  1,406 lines

  1. ###########################################################################
  2. # bibtex.tcl
  3. # This file contains a package of Tcl routines that add support for using
  4. # and maintaining BibTeX citation databases to Alpha. 
  5. #
  6. # See the accompanying file, "BibTeX Help", for a complete description.
  7. # (Unfortunately, it's a bit out of date right now - stay tuned (WTP 6/95))
  8. ###########################################################################
  9. # Notes: 
  10. # By default, only the required fields are included when a new bib entry 
  11. # is created.  You can select any other set of fields to be used by adding 
  12. # an appropriate entry to the 'myFld' array, following the example for the 
  13. # Article entry, further below.  You shouldn't change the 'rqdFld' or 
  14. # 'optFld' arrays, since these will (some day) be used for syntax checking.
  15. ###########################################################################
  16. # written by Tom Pollard (pollard@cucbs.chem.columbia.edu)
  17. #
  18. # Version History
  19. #
  20. # 2.7  (7/95)   'stdAbbrevs' modeVar added for setting predefined abbrevs
  21. #               month names included as predefined abbrevs
  22. #               'alignEquals' formatting flag added.
  23. # 2.62 (7/95)   field delimiters suppressed if field data is an abbreviation
  24. #               unindexed .bib files are indexed automatically upon opening
  25. # 2.61 (7/95)   fixed "SearchFields" bug.
  26. # 2.6  (6/95)   'zapEmptyFields' flag forces optional fields to be removed 
  27. #                   when reformatting an entry.
  28. #               'markStrings' flag controls whether @string entries are included in 
  29. #                   the marks menu.
  30. #               'descendingYears' flag controls whether sorts are in ascending or 
  31. #                   descending chronological order.
  32. #               Sorts all use the year as either primary or secondary sort key now.
  33. #               'copyCiteKey' command copies the citekey of the current entry to the
  34. #                   clipboard.
  35. #               Cmd-double-clicking implemented to resolve abbreviations and crossrefs.
  36. #               Fixed bug in faster getFields proc (comma-after-last-field problems)  
  37. #               Fixed minor bugs in author sorting.  
  38. # 2.5  (6/95)   Fixed bug in formatEntry, whereby '#' concatenations were lost 
  39. #               formatEntry completely ignores @string entries now
  40. #               Entry-parsing code (getFields, getFldVal) cleaned up,
  41. #                  should also be a little bit faster now.
  42. #               formatAllEntries now starts working from the current entry
  43. # 2.41 (6/95)   Updates for compatibility with revised LaTeX mode
  44. #               Automatic conversion of international characters dropped 
  45. #                   (irreconcilable problems with non-US keyboards).
  46. # 2.4  (5/95)   Fixed bugs in parsing of EndNote-created bib files
  47. # 2.3  (4/95)   International characters converted to TeX codes (optionally).
  48. #               'findEntries' bug fixed (no longer returns multiple hits) 
  49. # 2.2 (12/94)   'formatEntries' won't quote fields that contain "#".
  50. #               'segregateStrings' flag forces string defs to sort to the top.
  51. # 2.11(12/94)   Bug fixes in 'formatAllEntries'.
  52. # 2.1 (12/94)   'countEntries' command added.
  53. #               'formatAllEntries' command added; it's a bit clunky, but more robust
  54. #                   than any quicker alternative I considered.
  55. #               Cross-referenced entries now sort to the bottom in all sorts.
  56. #                'crossref' field now included.
  57. # 2.0  (9/94)   'formatEntry' and 'newEntry' line up fields better.
  58. #                'nextEntry' and 'prevEntry' skip @string defs
  59. #                'formatEntry' automtically goes to next entry afterwards.
  60. #                'sortByCitekey' ignores case of cite keys.
  61. #               'fillColumn' included as default modeVar.
  62. #                'getEntry' alerts user to badly delimited entries.
  63. # 1.9 (9/94)    'getFields' should now correctly parse any legal entry.
  64. #                'language' field now included.
  65. #                Default values for new fields (eg 'language') may be defined
  66. #                'preferBraces' replaced by 'fieldBraces' and 'entryBraces'.
  67. #                line-wrapping is done on reformatted entries.
  68. #                '@string' entries preserved in sorts.
  69. #                text before first entry and after last entry are preserved
  70. #                    by sorts.
  71. # 1.8 (8/94)    "getEntry" now recognizes parens as entry delimiters
  72. # 1.7 (8/94)    Bug fixes and accomodations to latex.tcl v2.2
  73. #               Template insertion streamlined
  74. #                Choose multiple fields at a time from list dialog
  75. # 1.6 (8/94)    "preferBraces" allows braces or quotes to be default for
  76. #                   new or reformatted entries,
  77. #               Menu built using $entryNames and $fieldNames,
  78. #               'sortByAuthors' can now sort using last author first,
  79. #                   and is a bit faster,
  80. #               'formatEntry' rewrites entries in canonical format,
  81. #               More customization of canonical format allowed ('indentString')
  82. #               Bib mode definition adapted to Alpha 5.90.
  83. # 1.5 (7/94)    "sortByAuthors" is now robust (I think),
  84. #               Mode of new windows now set correctly.
  85. # 1.4 (7/94)    Added sorting by authors, but still only semi-functional,
  86. #               Added regexp searching by field,
  87. #               "getEntry" bugs fixed.
  88. # 1.2 (7/94)    Bib mode definition adapted to Alpha 5.85,
  89. #               Added bib-file marking (bibMarkFile),
  90. #               Entry and field creation now controlled by data arrays.
  91. # 1.1 (6/94)    Custom BibTeX icon, 
  92. #               Added simple search capability (matchingEntries).
  93. # 1.0 (9/93)    First stable version.
  94. #
  95. ###########################################################################
  96. # This package was inspired by the LaTeX package (latex.tcl), written by
  97. #    Richard T. Austin  <austin@eecs.umich.edu>  , and (currently),
  98. #    Tom Scavo          <trscavo@syr.edu>
  99. #
  100. ###########################################################################
  101. ############################################################################
  102. # Cause latex.tcl to be loaded by calling a dummy procedure defined in that
  103. # file.  This is necessary to get the TeX menu, and to load the 8-bit ASCII
  104. # to TeX conversion routines.
  105. #
  106. dummyTeX
  107.  
  108. ###########################################################################
  109. # BibTeX Key Bindings.
  110. ###########################################################################
  111. # abbreviations:  <o> = option, <z> = control, <s> = shift, <c> = command
  112. #
  113. bind 'b' <sz>    selectEntry "Bib"
  114. bind 'n' <sz>    nextEntry "Bib"
  115. bind 'p' <sz>    prevEntry "Bib"
  116.  
  117. bind 'f' <sz>    searchFields "Bib"
  118. bind 'm' <sz>    searchEntries "Bib"
  119. bind 'l' <sz>    formatEntry "Bib"
  120.  
  121. # tab stops:
  122. bind    '\t'    nextTabStop    "Bib"
  123. bind    '\t'    <s>     prevTabStop    "Bib"
  124. bind  '\t'  <z>  {nthTabStop 0}  "Bib"
  125. bind  '\t'  <c>  deleteTabStops  "Bib"
  126.  
  127. ###########################################################################
  128. # Data Definitions
  129. ###########################################################################
  130. ###########################################################################
  131. # Define the data arrays that contain the names of the required,
  132. # optional, and preferred fields for each entry type.
  133. #
  134. # The index names of the rqdFld() array _define_ the valid entry types
  135. # recognized by the program.
  136. #
  137. set rqdFld(article) {author title journal year} 
  138. set optFld(article) {volume number pages month note}
  139. set myFld(article) {author title journal volume pages year note} 
  140.  
  141. set rqdFld(book) {author title publisher year} 
  142. set optFld(book) {editor volume number series address edition month note}
  143.  
  144. set rqdFld(booklet) {title} 
  145. set optFld(booklet) {author howpublished address month year note}
  146.  
  147. set rqdFld(conference) {author title booktitle year} 
  148. set optFld(conference) {editor volume number series pages organization publisher address month note}
  149.  
  150. set rqdFld(inBook) {author title chapter publisher year} 
  151. set optFld(inBook) {editor pages volume number series address edition month type note}
  152.  
  153. set rqdFld(inCollection) {author title booktitle publisher year} 
  154. set optFld(inCollection) {editor volume number series type chapter pages address edition month note}
  155.  
  156. set rqdFld(inProceedings) {author title booktitle year} 
  157. set optFld(inProceedings) {editor volume number series pages organization publisher address month note}
  158.  
  159. set rqdFld(manual) {title} 
  160. set optFld(manual) {author organization address edition year month note}
  161.  
  162. set rqdFld(mastersThesis) {author title school year} 
  163. set optFld(mastersThesis) {address month note type}
  164.  
  165. set rqdFld(misc) {} 
  166. set optFld(misc) {author title howpublished year month note}
  167.  
  168. set rqdFld(phdThesis) {author title school year} 
  169. set optFld(phdThesis) {address month type note}
  170.  
  171. set rqdFld(proceedings) {title year} 
  172. set optFld(proceedings) {editor volume number series publisher organization address month note}
  173.  
  174. set rqdFld(techReport) {author title institution year} 
  175. set optFld(techReport) {type number address month note}
  176.  
  177. set rqdFld(unpublished) {author title note} 
  178. set optFld(unpublished) {year month}
  179.  
  180. set entryNames [lsort [array names rqdFld]]
  181. set customEntries [lsort [array names myFld]]
  182.  
  183. ###########################################################################
  184. # Define an array of flags indicating whether the data a given field
  185. # type should be quoted.  The actual characters used to quote the field are
  186. # given by $bibOpenQuote and $bibCloseQuote, which are set by the routine
  187. # 'bibFieldDelims' according to the flag $fieldBraces.
  188. #
  189. # Note that the index names of the useBrace() array _define_ the valid 
  190. # field types recognized by the program.
  191. #
  192. set useBrace(address)    1
  193. set useBrace(annote)    1
  194. set useBrace(author)     1
  195. set useBrace(booktitle)    1
  196. set useBrace(chapter)    0
  197. set useBrace(crossref)    1
  198. set useBrace(edition)    1
  199. set useBrace(editor)    1
  200. set useBrace(howpublished)    1
  201. set useBrace(institution)    1
  202. set useBrace(journal)    1
  203. set useBrace(key)    1
  204. set useBrace(language)    1
  205. set useBrace(month)    1
  206. set useBrace(note)    1
  207. set useBrace(number)    0
  208. set useBrace(organization)    1
  209. set useBrace(pages)    0
  210. set useBrace(publisher)    1
  211. set useBrace(school)    1
  212. set useBrace(series)    1
  213. set useBrace(title)    1
  214. set useBrace(type)    1
  215. set useBrace(volume)    0
  216. set useBrace(year)    0
  217.  
  218. set fieldNames [lsort [array names useBrace]]
  219. ###########################################################################
  220. # Default values for newly created fields
  221. #
  222. set defFldVal(language) "german"
  223.  
  224. set fieldDefs [lsort [array names defFldVal]]
  225.  
  226. ###########################################################################
  227. # Search patterns for entries and cite-keys
  228. #
  229. #     set bibTopPat {^[     ]*@[a-zA-Z]+[\{\(]([-A-Za-z0-9_:/\.]+)}
  230. # match entry type
  231. set bibTopPat {^[     ]*@([a-zA-Z]+)[\{\(]}
  232. # match cite-key
  233. set bibTopPat1 {^[     ]*@[a-zA-Z]+[\{\(][     ]*([^=,     ]+)}    
  234. # match type and cite-key
  235. set bibTopPat2 {^[     ]*@([a-zA-Z]+)[\{\(][     ]*([^=,     ]+)}    
  236. # match first field (no cite-key)
  237. set bibTopPat3 {^[     ]*@([a-zA-Z]+)[\{\(]([     ]*[a-zA-Z]+[     ]*=[     ]*)}    
  238.                                                                 
  239.  
  240. ###########################################################################
  241. # BibTeX-mode mode definition
  242. ###########################################################################
  243. newModeVar Bib suffixString    { \\\\} 0
  244. newModeVar Bib prefixString    {% } 0
  245. newModeVar Bib fillColumn    {65} 0
  246. newModeVar Bib wordWrap        {0} 1
  247. newModeVar Bib autoMark        {1} 1
  248.  
  249. newModeVar Bib wordBreak         {[a-zA-Z0-9]+} 0
  250. newModeVar Bib wordBreakPreface     {[^a-zA-Z0-9]} 0
  251. newModeVar Bib funcExpr           $bibTopPat 0
  252.  
  253. newModeVar Bib overwriteBuffer {1} 1
  254. newModeVar Bib fieldBraces {1} 1
  255. newModeVar Bib entryBraces {1} 1
  256. newModeVar Bib segregateStrings {1} 1
  257. newModeVar Bib markStrings {0} 1
  258. newModeVar Bib alignEquals {0} 1
  259. ###
  260. # newModeVar Bib emacsBibMode {0} 1
  261. # newModeVar Bib addCiteKeys {0} 1
  262. # newModeVar Bib checkSyntax {0} 1
  263. newModeVar Bib zapEmptyFields {0} 1
  264. newModeVar Bib descendingYears {1} 1
  265. ###
  266. newModeVar Bib indentString {   } 0
  267. newModeVar Bib stdAbbrevs {jan feb mar apr may jun jul aug sep oct nov dec} 0
  268. # newModeVar Bib convert8bitAscii2TeX {0} 1
  269.  
  270. set bibtexKeyWords {address annote author booktitle 
  271.     chapter city crossref edition editor howpublished institution 
  272.     journal key language month note number organization 
  273.     publisher pages school series title type 
  274.     volume year}
  275. regModeKeywords -e {%} -m {@} -c red -k blue Bib $bibtexKeyWords
  276. unset bibtexKeyWords
  277.  
  278. # # Use a shadow proc to keep settings for 8-bit character conversion 
  279. # # consistent between TeX and Bib modes. 
  280. # #
  281. # trace variable BibmodeVars(convert8bitAscii2TeX) w shadowBib8bitConvert
  282. # proc shadowBib8bitConvert {name1 name2 op} {
  283. #     global BibmodeVars TeXmodeVars
  284. #     # Use TeX-mode routines to actually do the key bindings. 
  285. #     #
  286. #     if {$BibmodeVars(convert8bitAscii2TeX)} then {
  287. #         toggle8bitAscii "ascii" "Bib"
  288. #     } else {
  289. #         toggle8bitAscii "unascii" "Bib"
  290. #     }
  291. #     
  292. #     # Only set TeX flag if necessary, to avoid unnecessary rebinding of keys
  293. #     # (It takes enough time to be annoying)
  294. #     #
  295. #     if {$BibmodeVars(convert8bitAscii2TeX) != $TeXmodeVars(convert8bitAscii2TeX)} then {
  296. #         set TeXmodeVars(convert8bitAscii2TeX) $BibmodeVars(convert8bitAscii2TeX)
  297. #     }
  298. # }
  299. # set BibmodeVars(convert8bitAscii2TeX) $TeXmodeVars(convert8bitAscii2TeX)
  300.  
  301. ###########################################################################
  302. # BibTeX Menu Definition.
  303. ###########################################################################
  304. proc bibtexMenu {} {}
  305.  
  306. set bibtexMenu "•136"
  307.  
  308. proc bibtex {} {
  309.     global bibtexSig
  310.     set name [launchBackApplSigs {BIBt Vbib} bibtexSig]
  311.     switchTo [file tail $name]
  312. }
  313.  
  314. proc makeindex {} {
  315.     launchForeAppl Midx
  316. }
  317.  
  318. menu -n $bibtexMenu {
  319.     "bibtex"
  320.     "(-)"  
  321.     {menu -n Entries -p makeEntry {}
  322.     }
  323.     {menu -n Fields -p makeField {}
  324.     }
  325.     "(-)"
  326.     "selectEntry/B<U<B"
  327.     "nextEntry/N<U<B"
  328.     "prevEntry/P<U<B"
  329.     "formatEntry/L<U<B"
  330.     "copyCiteKey/C<U<B"
  331.     "(-)"
  332.     "searchEntries/M<U<B"
  333.     "searchFields/F<U<B"
  334.     {menu -n sortBy... -p bibSortProc {
  335.         "citeKey"
  336.         "firstAuthor,Year"
  337.         "lastAuthor,Year"
  338.         "year,FirstAuthor"
  339.         "year,LastAuthor"
  340.         }
  341.     }
  342.     {menu -n sortMarks... -p markSortProc {
  343.         "alphabetically"
  344.         "byPosition"
  345.         }
  346.     }
  347.     "(-)"
  348.     "countEntries"
  349.     "formatAllEntries"
  350.     
  351. menu -n Entries -p makeEntry [concat $entryNames {
  352.         "(-)"
  353.         "customEntry"
  354.         } ]
  355.  
  356. menu -n Fields -p makeField [concat $fieldNames {
  357.         "(-)"
  358.         "customField"
  359.         "multipleFields"
  360.         } ]
  361.         
  362. ###########################################################################
  363. # Menu command procs
  364. ###########################################################################
  365.         
  366. proc makeField {menu item} {
  367.     global fieldNames
  368.     bibFormatSetup
  369.     
  370.     if {$item == "multipleFields"} then {
  371.         set flds [listpick -l -L {author year} -p "Pick desired fields:" $fieldNames]
  372.         if {[llength flds]} {
  373.             set lines {}
  374.             foreach fld $flds {
  375.                 append lines [newField $fld]
  376.             }
  377.         } else {
  378.             return
  379.         }
  380.     } else {
  381.         set lines [newField $item]
  382.     }
  383.     
  384.     set pos0 [nextLineStart [getPos]]
  385.     goto $pos0
  386.     insertText $lines
  387.     goto $pos0
  388.     nextTabStop
  389. }
  390.  
  391. proc makeEntry {menu item} {
  392.     bibFormatSetup
  393.     newEntry $item
  394. }
  395.  
  396. ###########################################################################
  397. #  Return the bounds of the bibliographic entry surrounding the current 
  398. #  position.
  399. #
  400. proc getEntry {pos} {
  401.     
  402.      set pos1 [search -f 0 -r 1 -n -s {[     ]*@[a-zA-Z]*[\{\(]} $pos ]
  403.     if {$pos1 == ""} then {
  404.         set begPos [nextLineStart $pos]
  405.         set endPos $begPos
  406.     } else {
  407.         set begPos [lineStart [lindex $pos1 0]]
  408.         set pos0 [lindex $pos1 1]
  409.         set openBrace [getText [expr $pos0-1] $pos0 ]
  410.         if {[catch {matchIt $openBrace $pos0} pos1]} {
  411.             alertnote "There seems to be a badly delimited field in here.  Are entry and field delimiters set correctly?"
  412.             goto $begPos
  413.             error "Can't find close brace"
  414.         } else {
  415.             set endPos [nextLineStart $pos1]
  416.         }
  417.     }
  418.     return [list $begPos $endPos]
  419. }
  420.  
  421. ###########################################################################
  422. #  Advance to the next bibliographic entry.
  423. #
  424. proc nextEntry {} {
  425.     global bibTopPat bibTopPat1 bibTopPat2
  426. #     set topPat {[     ]*@([a-zA-Z]+)[\{\(]}
  427.     
  428.     set pos0 [lindex [getEntry [getPos]] 1]
  429.     set nextPos [nextLineStart $pos0]
  430.     
  431.     while {![catch {search -f 1 -r 1 -s $bibTopPat $pos0} pos]} {
  432.         regexp $bibTopPat [eval getText $pos] mtch type
  433.         if {$type != "string"} {
  434.             set nextPos [lindex $pos 0]
  435.             break
  436.         } else {
  437.             set pos0 [nextLineStart [lindex $pos 1]]
  438.         }
  439.     }
  440.     goto $nextPos
  441. }
  442.  
  443. ###########################################################################
  444. #  Go back to the previous bibliographic entry.
  445. #
  446. proc prevEntry {} {
  447.     global bibTopPat bibTopPat1 bibTopPat2
  448. #     set topPat {[     ]*@([a-zA-Z]+)[\{\(]}
  449.     
  450.     set pos0 [lindex [getEntry [getPos]] 0]
  451.     if {$pos0 > 0} {
  452.         set nextPos $pos0
  453.         incr pos0 -1
  454.         while {![catch {search -f 0 -r 1 -s $bibTopPat $pos0} pos]} {
  455.             regexp $bibTopPat [eval getText $pos] mtch type
  456.             if {$type != "string"} {
  457.                 set nextPos [lindex $pos 0]
  458.                 break
  459.             } else {
  460.                 set pos0 [lineStart [lindex $pos 0]]
  461.                 if {$pos0 == 0} {break}
  462.                 incr pos0 -1
  463.             }
  464.         }
  465.         goto $nextPos
  466.     }
  467. }
  468.  
  469. ###########################################################################
  470. #  Select (highlight) the current bibliographic entry.
  471. #
  472. proc selectEntry {} {
  473.     set pos [getEntry [getPos]]
  474.     select [lindex $pos 0] [lindex $pos 1]
  475. }
  476.  
  477. ###########################################################################
  478. #  Put the cite-key of the current entry on the clipboard.
  479. #
  480. proc copyCiteKey {} {
  481.     global bibTopPat2
  482.     set limits [getEntry [getPos]]
  483.     set top [lindex $limits 0]
  484.     set bottom [lindex $limits 1]
  485.     if {[regexp -indices $bibTopPat2 [getText $top $bottom] allofit type citekey]} {
  486.         select [expr $top+[lindex $citekey 0]] [expr $top+[lindex $citekey 1]+1]
  487.         copy
  488.         message "Copied \"[getSelect]\""
  489.     } 
  490. }
  491.  
  492. ###########################################################################
  493. #  Create a new bibliographic entry with its required fields.
  494. #
  495. proc newEntry {entryName} {    
  496.     global  entryNames customEntries fieldNames rqdFld optFld myFld defFldVal
  497.     global bibOpenEntry bibCloseEntry BibmodeVars
  498.     goto [lindex [getEntry [getPos]] 1]
  499.     if {$entryName == "customEntry"} {
  500.         set lines "@•$bibOpenEntry•,\r"
  501.         set theFields [listpick -l -L {author} -p "Pick desired fields:" $fieldNames]
  502.     } else {
  503.         set lines "@${entryName}$bibOpenEntry•,\r"
  504.         if {[lsearch -exact $customEntries $entryName] >= 0 && [llength $myFld($entryName)]} {
  505.             set theFields $myFld($entryName)
  506.         } elseif {[lsearch -exact $entryNames $entryName] >= 0} {
  507.             set theFields $rqdFld($entryName)
  508.         } else {
  509.             set theFields {}
  510.         }
  511.     }
  512.     set nmlen 0
  513.     foreach field $theFields {
  514.         set len [string length $field]
  515.         if {$len > $nmlen} {set nmlen $len}        
  516.     }
  517.     set theTop [lineStart [getPos]]
  518.     foreach field $theFields {
  519.         catch {append lines [newField $field $nmlen]}
  520.     }
  521.     append lines "$bibCloseEntry\r"
  522.     insertText $lines
  523.     goto $theTop
  524.     nextTabStop
  525. }
  526.  
  527. ###########################################################################
  528. #  Create a new field within the current bibliographic entry
  529. #
  530. proc newField {fieldName {nmlen 0}} {    
  531.     global fieldNames useBrace bibOpenQuote bibCloseQuote bibIndent
  532.     global fieldDefs defFldVal
  533.     set spc "                   "
  534.     if {[lsearch -exact $fieldNames $fieldName] >= 0} {
  535.         set needBraces $useBrace($fieldName)
  536.     } else {
  537.         set needBraces 1
  538.     }
  539.     
  540.     if {[lsearch -exact $fieldDefs $fieldName] >= 0} {
  541.         set val $defFldVal($fieldName)
  542.     } else {
  543.         set val "•"
  544.     }
  545.     
  546.     if {$nmlen} {
  547.         set pad [string range $spc 1 [expr $nmlen - [string length $fieldName]]]
  548.     } else {
  549.         set pad ""
  550.     }            
  551.     if {$needBraces || $fieldName == "customField"} {
  552.         set result "$bibIndent$fieldName =$pad ${bibOpenQuote}${val}${bibCloseQuote},\r"
  553.     } else {
  554.         set result "$bibIndent$fieldName =$pad $val,\r"
  555.     }    
  556.     return $result
  557. }
  558.  
  559. proc bibFormatSetup {} {
  560.     global bibOpenQuote bibCloseQuote bibIndent BibmodeVars
  561.     global bibOpenEntry bibCloseEntry bibAbbrevs
  562.     bibFieldDelims
  563.     bibEntryDelims
  564.     set bibIndent $BibmodeVars(indentString)
  565.     regsub {\\t} $bibIndent {    } bibIndent
  566.     set bibAbbrevs [listStrings]
  567.     foreach abbrev $BibmodeVars(stdAbbrevs) {
  568.         lappend bibAbbrevs [string tolower $abbrev]
  569.     }
  570. }
  571.  
  572. ###########################################################################
  573. #  Find all entries that match a given regular expression and copy them to 
  574. #  a new buffer.
  575. #
  576. proc searchEntries {} {
  577.     if [catch {prompt "Regular expression:" ""} reg] return
  578.     if {![string length $reg]} return
  579.     set reg ^.*$reg.*$
  580.     
  581.     set matches [findEntries $reg]
  582.     if {[llength $matches] >0} {
  583.         writeEntries $matches 0
  584.     } else {
  585.         message "No matching entries were found"
  586.     }
  587. }
  588.  
  589. ###########################################################################
  590. #  Find all entries in which the indicated field matches a given regular 
  591. #  expression and copy them to a new buffer.  
  592. #
  593. proc searchFields {} {
  594.     global fieldNames
  595.     if {[catch {eval prompt {{Field name:}} "author" {Fields} $fieldNames} fld]} return
  596.     if {![string length $fld]} return
  597.  
  598.     if {[catch {prompt "Regular expression:" ""} reg]} return
  599.     if {![string length $reg]} return
  600.  
  601.     set matches [findEntries $reg]
  602.     if {[llength $matches] == 0} {
  603.         return "No matching entries were found"
  604.     }
  605.     
  606.     set vals {}
  607.     foreach hit $matches {
  608.         set pos [lindex $hit 1]
  609.         set top [lindex $hit 2] 
  610.         set bottom [lindex $hit 3]
  611.         while {[set failure [expr {[getFldName $pos $top] != $fld}]]  && 
  612.             ![catch {search -f 1 -r 1 -i 1 -m 0 -l $bottom -s -- $reg $pos} mtch]} {
  613.             set pos [lindex $mtch 1]
  614.         }
  615.         if {!$failure} { lappend vals [list $top $bottom] }
  616.     }
  617.     
  618.     if {[llength $vals] >0} {
  619.         writeEntries $vals 0
  620.     } else {
  621.         message "No matching entries were found"
  622.     }
  623.     
  624. }
  625.  
  626. ###########################################################################
  627. # Sort all of the entries based on one of various criteria.
  628. #
  629. proc bibSortProc {menu item} {
  630.     if {$item == "citeKey"} {
  631.         sortByCiteKey
  632.     } elseif  {$item == "firstAuthor,Year"} {
  633.         sortByAuthors 0 0
  634.     } elseif  {$item == "lastAuthor,Year"} {
  635.         sortByAuthors 1 0
  636.     } elseif  {$item == "year,FirstAuthor"} {
  637.         sortByAuthors 0 1
  638.     } elseif  {$item == "year,LastAuthor"} {
  639.         sortByAuthors 1 1
  640.     }
  641. }
  642.  
  643. ###########################################################################
  644. # Sort the file marks. (These operations are also available under the
  645. # "Search:NamedMarks" menu)
  646. #
  647. proc markSortProc {menu item} {
  648.     if {$item == "alphabetically"} {
  649.         sortMarksFile
  650.     } elseif  {$item == "byPosition"} {
  651.         orderMarks
  652.     }
  653. }
  654.  
  655. ###########################################################################
  656. # Sort all of the entries in the file alphabetically by author.
  657. #
  658. proc sortByAuthors {{lastAuthorFirst 0} {yearFirst 0}} {
  659.     global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
  660.     set bibSegStr $BibmodeVars(segregateStrings)
  661.     
  662.     set matches [findEntries $bibTopPat]
  663.     set crossrefs [listCrossrefs]
  664.     set strings [listStrings]
  665.     
  666.     set vals {}
  667.     set others {}
  668.     set refs {}
  669.     set strs {}
  670.     
  671.     set beg [maxPos]
  672.     set end 0
  673.     
  674.     foreach hit $matches {
  675.         set pos [lindex $hit 1]
  676.         set top [lindex $hit 2] 
  677.         set bottom [lindex $hit 3]
  678.         set entry [getText $top $bottom]
  679.         regsub -all "\[\n\r\]+" $entry { } entry
  680.         regsub -all "\[     \]\[     \]+" $entry { } entry
  681.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  682.         if {[regexp $bibTopPat1 $entry allofit citeKey]} {
  683.             set citeKey [string tolower $citeKey]
  684.             set keyExists 1
  685.         } else {
  686.             set citekey ""
  687.             set keyExists 0
  688.         }
  689.         
  690.         if {$keyExists && [lsearch -exact $crossrefs $citeKey] >= 0} {
  691.             lappend refs [list $pos $top $bottom]
  692.         } elseif {$bibSegStr && $keyExists && [lsearch -exact $strings $citeKey] >= 0} {
  693.             lappend strs [list $citeKey $top $bottom]        
  694.         } else {
  695.             if {![catch {getFldValue $entry author} fldval]} {
  696.                 if {[catch {getFldValue $entry year} year]} { set year 9999 }
  697.                 lappend vals [list [authSortKey $fldval $lastAuthorFirst $year $yearFirst] $top $bottom]
  698.             } else {
  699.                 lappend others [list $pos $top $bottom]
  700.             }
  701.         }
  702.         if {$top < $beg} {set beg $top}
  703.         if {$bottom > $end} {set end $bottom}
  704.     }
  705.     
  706.     if {$bibSegStr} {
  707.         set result [concat $strs $others [lsort $vals] $refs]
  708.     } else {
  709.         set result [concat $others [lsort $vals] $refs]
  710.     }
  711.     
  712.     if {[llength $result] >0} {
  713.         writeEntries $result 1 $beg $end
  714.     } else {
  715.         message "No results of author sort !!??"
  716.     }
  717. }
  718.  
  719. ###########################################################################
  720. # Return a list of the cite-keys of all cross-referenced entries.
  721. #
  722. proc listStrings {} {
  723.     global bibTopPat bibTopPat1 bibTopPat2
  724.     set matches [findEntries {^[    ]*@string *[\{\(]} 0]
  725.  
  726.     message "scanning for @strings…"
  727.     foreach hit $matches {
  728.         set top [lindex $hit 2] 
  729.         set bottom [lindex $hit 3]
  730.         set entry [getText $top $bottom]
  731.         regsub -all "\[\n\r\]+" $entry { } entry
  732.         regsub -all "\[     \]\[     \]+" $entry { } entry
  733.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  734.         regexp $bibTopPat1 $entry allofit citekey
  735.         set citekey [string tolower $citekey]
  736.         if {[catch {incr strings($citekey)} num]} {
  737.             set strings($citekey) 1
  738.         }
  739.     }
  740.     if {[catch {lsort [array names strings]} res]} {
  741.         set res {}
  742.     }
  743.     message ""
  744.     return $res
  745. }
  746.  
  747. ###########################################################################
  748. # Return a list of the cite-keys of all cross-referenced entries.
  749. #
  750. proc listCrossrefs {} {
  751.     set matches [findEntries {crossref}]
  752.     catch {unset crossrefs}
  753.  
  754.     message "scanning for crossrefs…"
  755.     foreach hit $matches {
  756.         set top [lindex $hit 2] 
  757.         set bottom [lindex $hit 3]
  758.         set entry [getText $top $bottom]
  759.         regsub -all "\[\n\r\]+" $entry { } entry
  760.         regsub -all "\[     \]\[     \]+" $entry { } entry
  761.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  762.         if {![catch {getFldValue $entry crossref} fldval]} {
  763.             set fldval [string tolower $fldval]
  764.             if {[catch {incr crossref($fldval)} num]} {
  765.                 set crossrefs($fldval) 1
  766.             }
  767.         }
  768.     }
  769.     if {[catch {lsort [array names crossrefs]} res]} {
  770.         set res {}
  771.     }
  772.     message ""
  773.     return $res
  774. }
  775.  
  776. ###########################################################################
  777. # Create a sort key from an author list.  When sorting entries by author, 
  778. # performing the sort using keys should be faster than reparsing the author 
  779. # lists for every comparison (the old method :-( ).
  780. #
  781. proc authSortKey {authList lastAuthorFirst {year {}} {yearFirst 0}} {
  782.     global BibmodeVars
  783.     set pat1 {\\.\{([A-Za-z])\}}
  784.     set pat2 {\{([^\{\}]+) ([^\{\}]+)\}}
  785.  
  786. # Remove enclosing braces, quotes, or whitespace
  787.     set auths %[string trim $authList {{}"     }]&
  788. # Remove TeX codes for accented characters
  789.     regsub -all $pat1 $auths {\1} auths
  790. # Concatenate strings enclosed in braces
  791.     while {[regsub -all $pat2 $auths {{\1\2}} auths]} {}
  792. # Remove braces (curly and square)
  793.     regsub -all {[][\{\}]} $auths {} auths
  794. #    regsub -all {,} $auths { ,} auths
  795. # Replace 'and's with begin-name/end-name delimiters
  796.     regsub -all {[     ]and[     ]} $auths { \&% } auths
  797. # Put last name first in name fields without commas
  798.     regsub -all {%([^\&,]+) ([^\&, ]+) *\&} $auths {%\2,\1\&} auths
  799. # Remove begin-name delimiters
  800.     regsub -all {%} $auths {} auths
  801. # Remove whitespace surrounding name separators
  802.     regsub -all {[     ]*\&[     ]*} $auths {\&} auths
  803. # Replace whitespace separating words with shrieks 
  804.     regsub -all {[     ,]+} $auths {!} auths
  805. # If desired, move last author to head of sort key
  806.     if {$lastAuthorFirst} {
  807.         regsub {(.*)&([^&]+)&?$} $auths {\2\&\1} auths
  808.     }
  809. # If provided, sort by year (descending order) as well
  810.     regsub {^[^0-9]*([0-9]*).*$} $year {\1} year
  811.     if {$year != {}} {
  812.         if {$BibmodeVars(descendingYears)} { catch {set year [expr 9999-$year]} }
  813.         if {$yearFirst} {
  814.             set auths "$year&$auths"
  815.         } else {        
  816.             regsub {^([^&]+)(&?)} $auths "\\1\\&${year}\\2" auths
  817.         }
  818.     }
  819.         
  820.     return $auths
  821. }
  822.  
  823. ###########################################################################
  824. # Sort all of the entries in the file alphabetically by their cite-keys.
  825. #
  826. proc sortByCiteKey {} {
  827.     global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
  828.     set bibSegStr $BibmodeVars(segregateStrings)
  829.     
  830.     set matches [findEntries $bibTopPat]
  831.     set crossrefs [listCrossrefs]
  832.     set strings [listStrings]
  833.  
  834.     set begEntries [maxPos]
  835.     set endEntries 0
  836.     
  837.     set strs {}
  838.     set vals {}
  839.     set refs {}
  840.         
  841.     foreach hit $matches {
  842.         set beg [lindex $hit 0]
  843.         set end [lindex $hit 1]
  844.         set top [lindex $hit 2] 
  845.         set bottom [lindex $hit 3]
  846.         if {[regexp $bibTopPat1 [getText $top $bottom] allofit citekey]} {
  847.             set citekey [string tolower $citekey]
  848.             set keyExists 1
  849.         } else {
  850.             set citekey "000000$beg"
  851.             set keyExists 0
  852.         }
  853.         
  854.         if {$keyExists && [lsearch -exact $crossrefs $citekey] >= 0} {
  855.             lappend refs [list $top $top $bottom]
  856.         } elseif {$keyExists && $bibSegStr && [lsearch -exact $strings $citekey] >= 0} {
  857.             lappend strs [list $citekey $top $bottom]        
  858.         } else {
  859.             lappend vals [list $citekey $top $bottom]
  860.         }
  861.  
  862.         if {$top < $begEntries} {set begEntries $top}
  863.         if {$bottom > $endEntries} {set endEntries $bottom}
  864.     }
  865.  
  866.     if {$bibSegStr} {
  867.         set result [concat $strs [lsort $vals] $refs]
  868.     } else {
  869.         set result [concat [lsort $vals] $refs]
  870.     }
  871.     
  872.     if {[llength $result] >0} {
  873.         writeEntries $result 1 $begEntries $endEntries
  874.     } else {
  875.         message "No results of cite-key sort !!??"
  876.     }
  877. }
  878.  
  879. ###########################################################################
  880. # Search for all entries matching a given regular expression.  The results
  881. # are returned in a list, each element of which is a list of four integers:
  882. # the beginning and end of the matching entry and the beginning and end of
  883. # the matching string.  Adapted from "matchingLines" in "misc.tcl".
  884. #
  885. proc findEntries {reg {casesen 1}} {
  886.     if {![string length $reg]} return
  887.     
  888.     set pos 0   
  889.     set result {}                             
  890.     while {![catch {search -f 1 -r 1 -m 0 -i $casesen -s $reg $pos} mtch]} {
  891.         set entry [getEntry [lindex $mtch 0]]
  892.         lappend result [concat $mtch $entry]
  893.         set pos [lindex $entry 1]
  894.     }
  895.     return $result
  896. }
  897.  
  898. ###########################################################################
  899. #  Return a list containing the data for the current entry, indexed by
  900. #  the parameter names, e.g., "author", "year", etc.  Index names for the 
  901. #  entry type and cite-key are "type" and "citekey". 
  902. #
  903. proc getFields {pos} {
  904.      global bibTopPat bibTopPat1 bibTopPat2 bibTopPat3
  905.     set fldPat {[     ]*([a-zA-Z]+)[     ]*=[     ]*}
  906.  
  907.     set limits [getEntry $pos]
  908.     set top [lindex $limits 0]
  909.     set bottom [lindex $limits 1]
  910.     
  911.     set entry [getText $top $bottom]
  912.     regsub -all "\[\n\r\]+" $entry { } entry
  913.     regsub -all "\[     \]\[     \]+" $entry { } entry
  914. #
  915.     regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  916.  
  917.     if {[regexp -indices $bibTopPat2 $entry mtch theType theKey ]} {
  918.         set key [string range $entry [lindex $theKey 0] [lindex $theKey 1]]
  919.         set theRest [expr 1 + [lindex $mtch 1]]
  920.     } elseif {[regexp -indices $bibTopPat3 $entry mtch theType aField]} {
  921.         set key {}
  922.         set theRest [lindex $aField 0]
  923.     } else {
  924.         error "Invalid entry"
  925.     }
  926.     lappend names type
  927.     set type [string tolower [string range $entry [lindex $theType 0] [lindex $theType 1]]]
  928.     lappend data [list $type]
  929.  
  930.     lappend names citekey
  931.     lappend data $key
  932.     
  933.     set entry ",[string range $entry $theRest end]"
  934.     set fldPat {,[     ]*([^ =,]+)[     ]*=[     ]*}
  935.     set name {}
  936.     while {[regexp -indices $fldPat $entry mtch sub1]} {
  937.         set nextName [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
  938.         lappend names [string tolower $nextName]
  939.         if {$name != ""} { 
  940.             set prevData [string range $entry 0 [expr [lindex $mtch 0]-1]]
  941.             lappend data [breakIntoLines [bibFieldData $prevData]]
  942.         }    
  943.         set name $nextName
  944.         set entry [string range $entry [expr [lindex $mtch 1]+1] end]
  945.     }
  946.  
  947.     lappend data [breakIntoLines [bibFieldData $entry]]
  948.     
  949.     return [list $names $data]
  950. }
  951.  
  952. proc bibFieldData {text} {
  953.     set text [string trim $text {     ,#}]
  954.     set text1 [string trim $text {\{\}\"     }]            
  955.     
  956.     if {[string match {*[\{\}\"]*} $text1]} {
  957.         set words [parseWords $text]
  958.         if {[llength $words]==1} {
  959.             regsub {^[\{\"\']} $text {} text
  960.             regsub {[\}\"\']$} $text {} text
  961.         }
  962.     } else {
  963.         set text $text1            
  964.     }
  965.     return $text
  966. }
  967.  
  968.  
  969. ###########################################################################
  970. # Extract the data from the indicated field of an entry, which is passed 
  971. # as a single string.  This version tries to be completely general, 
  972. # allowing nested braces within data fields and ignoring escaped 
  973. # delimiters.  (derived from proc getField).
  974. #
  975. proc getFldValue {entry fldname} {
  976.     set fldPat "\[     \]*${fldname}\[     \]*=\[     \]*"
  977.     set fldPat2 {,[     ]*([^ =,]+)[     ]*=[     ]*}
  978.     set slash "\\"
  979.     set qslash "\\\\"
  980.     
  981.     set ok [regexp -indices -nocase $fldPat $entry mtch]
  982.     if {$ok} {
  983.         set pos [expr [lindex $mtch 1] + 1]
  984.         set entry [string range $entry $pos end]
  985.         
  986.         if {[regexp -indices $fldPat2 $entry mtch sub1]} {
  987.             set entry [string range $entry 0 [expr [lindex $mtch 0]-1]]
  988.         } 
  989.         set fld [bibFieldData $entry]
  990.         
  991.         return $fld
  992.         
  993.     } else {
  994.         error "field not found"
  995.     }
  996. }
  997.  
  998. ###########################################################################
  999. # Parse the entry around position "pos" and rewrite it to the original 
  1000. # buffer in a canonical format
  1001. #
  1002. proc formatEntry {} {
  1003.     global useBrace bibOpenQuote bibCloseQuote 
  1004.     global bibOpenEntry bibCloseEntry bibIndent
  1005.     set spc "                           "
  1006.     
  1007.     bibFormatSetup
  1008.     
  1009.     set pos [getPos]
  1010.     set limits [getEntry $pos]
  1011.     set top [lindex $limits 0]
  1012.     set bottom [lindex $limits 1]
  1013.     
  1014.     if {![catch {bibFormatEntry $pos} result]} {
  1015.         set oldEntry [getText $top $bottom]
  1016.         if {$result != $oldEntry} {
  1017.             deleteText $top $bottom 
  1018.             insertText $result
  1019.         } 
  1020.         goto $top 
  1021.         nextEntry
  1022.     } else {
  1023.         message "Couldn't format this entry for some reason"
  1024.     }
  1025. }
  1026.  
  1027. ###########################################################################
  1028. # Parse the entry around position "pos" and rewrite it to the original 
  1029. # buffer in a canonical format
  1030. #
  1031. proc formatAllEntries {} {
  1032.     global useBrace bibOpenQuote bibCloseQuote 
  1033.     global bibOpenEntry bibCloseEntry bibIndent
  1034.     set spc "                           "
  1035.     
  1036.     bibFormatSetup
  1037.     
  1038.     # This little dance handles the case that the first 
  1039.     # entry starts on the first line
  1040.     #
  1041.     set hit [getEntry [getPos]]
  1042.     if {[lindex $hit 0] == [lindex $hit 1]} {
  1043.         nextEntry
  1044.         set hit [getEntry [getPos]]
  1045.     }
  1046.     
  1047.     while {[getPos] < [lindex $hit 1]} {
  1048.         set top [lindex $hit 0] 
  1049.         set bottom [lindex $hit 1]
  1050.         
  1051.         if {![catch {bibFormatEntry $top} result]} {
  1052.             set oldEntry [getText $top $bottom]
  1053.             if {$result != $oldEntry} {
  1054.                 deleteText $top $bottom 
  1055.                 insertText $result
  1056.             } 
  1057.         }
  1058.         goto $top
  1059.         nextEntry
  1060.         set hit [getEntry [getPos]]
  1061.     }
  1062. }
  1063.  
  1064. ###########################################################################
  1065. # Parse the entry around position "pos" and rewrite it in a canonical format.
  1066. # The formatted entry is returned.
  1067. #
  1068. proc bibFormatEntry {pos} {
  1069.     global useBrace bibOpenQuote bibCloseQuote 
  1070.     global bibOpenEntry bibCloseEntry bibIndent
  1071.     global rqdFld optFld BibmodeVars bibAbbrevs
  1072.     set spc "                           "
  1073.     #    
  1074.     #    note: calling proc must call "bibFormatSetup" before calling "bibFormatEntry"
  1075.     #
  1076.     set limits [getEntry $pos]
  1077.     set top [lindex $limits 0]
  1078.     set bottom [lindex $limits 1]
  1079.  
  1080.     if {[catch {getFields $pos} flds]} {
  1081.         error "bibFormatEntry: Getflds couldn't find any"
  1082.     }
  1083.     
  1084.     set names [lindex $flds 0]
  1085.     set vals [lindex $flds 1]
  1086.     set nfld [llength $names]
  1087.     
  1088.     set type [string tolower [lindex $vals 0]]
  1089.     set citekey [lindex $vals 1]
  1090. #     message "$citekey"
  1091.     # Don't process @string entries
  1092.     if {$type == "string"} {
  1093.         set lines [getText $top $bottom]
  1094.         return $lines
  1095.     }
  1096.     # Find length of longest field name
  1097.     set nmlen 0
  1098.     foreach nm $names {
  1099.         set len [string length $nm]
  1100.         if {$len > $nmlen} { set nmlen $len }
  1101.         if {![info exists useBrace($nm)]} { set useBrace($nm) 0 }
  1102.     }
  1103.     
  1104.     # Format first line
  1105.     set lines "@${type}${bibOpenEntry}${citekey},\r"
  1106.     
  1107.     # Format each field on a separate line
  1108.     for {set ifld 2} {$ifld < $nfld} {incr ifld} { 
  1109.     set nm [lindex $names $ifld]
  1110.     set vl [lindex $vals $ifld]
  1111.     if {$vl != "" || ! $BibmodeVars(zapEmptyFields) || 
  1112.             [lsearch $rqdFld($type) $nm] >= 0} {
  1113.         set pad [expr $nmlen - [string length $nm]]
  1114.         
  1115.         if {$BibmodeVars(alignEquals)} {
  1116.             set pref "${bibIndent}$nm[string range $spc 1 $pad] ="
  1117.         } else {
  1118.             set pref "${bibIndent}$nm =[string range $spc 1 $pad]"
  1119.         }
  1120.         set ind [string range $spc 1 [string length $pref]]
  1121.         
  1122.         # Delimit field, if appropriate
  1123.         set noBrace [expr ($useBrace($nm) == 0 && [isNum $vl]) || [hasCat $vl]]
  1124.         if {$noBrace == 0 && [string first " " $vl] < 0} {
  1125.             set noBrace [expr [lsearch $bibAbbrevs [string tolower $vl]] >= 0]
  1126.         }
  1127.         if {$noBrace != 0} {
  1128.             set vl "$vl,"
  1129.         } else {
  1130.             set vl "${bibOpenQuote}${vl}${bibCloseQuote},"
  1131.         }
  1132.         
  1133.         set pieces [split $vl "\r"]
  1134.         append lines "$pref [lindex $pieces 0]\r"
  1135.         foreach piece [lrange $pieces 1 end] {
  1136.             append lines "$ind  $piece\r"
  1137.         }
  1138.     }
  1139. }
  1140.     append lines "$bibCloseEntry\r"
  1141.     return $lines
  1142. }
  1143.  
  1144. ###########################################################################
  1145. # Get the name of the field that starts before the given position,  
  1146. # $pos.  The positions $top and $bottom restrict the range of the 
  1147. # search for the beginning and end of the field; typically, $top and
  1148. # $bottom will be the limits of a given entry.
  1149. #
  1150. proc getFldName {pos top} {
  1151.     set fldPat {[,     ]+([^     =,\{\}\"\']+)[     ]*=[     ]*}
  1152.     if {![catch {search -f 0 -r 1 -m 0 -i 1 -s -limit $top "$fldPat" $pos} mtch]} {
  1153.         set theText [eval getText $mtch]
  1154.         regexp -nocase $fldPat $theText allofit fldnam
  1155.         return $fldnam
  1156.     } else {
  1157.         return {citekey}
  1158.     }
  1159. }
  1160.  
  1161. ###########################################################################
  1162. #  Set the quote characters for quoted fields based on the value of the 
  1163. #  flag $bibUseBrace
  1164. proc bibFieldDelims {} {
  1165.     global BibmodeVars bibOpenQuote bibCloseQuote
  1166.     if {$BibmodeVars(fieldBraces)} then {
  1167.         set bibOpenQuote "{"
  1168.         set bibCloseQuote "}" 
  1169.     } else {
  1170.         set bibOpenQuote {"} 
  1171.         set bibCloseQuote {"} 
  1172.     }
  1173. }
  1174.  
  1175. proc bibEntryDelims {} {
  1176.     global BibmodeVars bibOpenEntry bibCloseEntry
  1177.     if {$BibmodeVars(entryBraces)} then {
  1178.         set bibOpenEntry "{"
  1179.         set bibCloseEntry "}" 
  1180.     } else {
  1181.         set bibOpenEntry "("
  1182.         set bibCloseEntry ")"
  1183.     }
  1184. }
  1185.  
  1186. proc isBibFile {} {
  1187.     set fileName [car [winNames -f]]   
  1188.     set ext [file extension $fileName]
  1189.     return [string match ".bib" [string tolower $ext]] 
  1190. }
  1191.  
  1192. proc hasNumVal {str} {
  1193.     expr ! [catch {expr $str}]
  1194. }
  1195. proc isNum {str} {
  1196.     regexp {^[     ]*[0-9]+[     ]*$} $str mtch
  1197. }
  1198. proc hasCat {str} {
  1199.     regexp {\#} $str mtch
  1200. }
  1201.  
  1202. ###########################################################################
  1203. # Take a list of lists that point to selected entries and copy these into
  1204. # a new window.  The beginning and ending positions for each entry must 
  1205. # be the last two items in each sublist.  The rest of the sublists are
  1206. # ignored.  It is assumed that each sublist has the same number of items.
  1207. #
  1208. proc writeEntries {entryPos nondestructive {beg {0}} {end {-1}}} {
  1209.         global BibmodeVars
  1210.         if {$end < 0} {set end [maxPos]}
  1211.         set llen [expr [llength [lindex $entryPos 0]] - 1]
  1212.         set llen1 [expr $llen-1]
  1213.         foreach entry $entryPos {
  1214.             set limits [lrange $entry $llen1 $llen]
  1215.             append lines [eval getText $limits]
  1216.         }
  1217.         set overwriteOK [expr $nondestructive || ! [isBibFile]]
  1218.         if {$BibmodeVars(overwriteBuffer) && $overwriteOK} {
  1219.             deleteText $beg $end
  1220.             insertText $lines
  1221.             goto $beg
  1222.         } else {
  1223.             set begLines [getText 0 [lineStart $beg]]
  1224.             set endLines [getText [nextLineStart $end] [maxPos]]
  1225.             new -n {*BibTeX Sort/Search*}
  1226.             newMode Bib
  1227.             insertText $begLines
  1228.             insertText $lines
  1229.             insertText $endLines
  1230.             goto $beg
  1231.             setWinInfo dirty 0
  1232.             catch shrinkWindow
  1233.         }
  1234. }
  1235.  
  1236. ###########################################################################
  1237. # Set a named mark for each entry, using the cite-key name
  1238. #
  1239. proc BibMarkFile {} {
  1240.     global BibmodeVars
  1241.      global bibTopPat bibTopPat1 bibTopPat2
  1242.     set pos 0
  1243.     while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat1 $pos} res]} {
  1244.         set start [lindex $res 0]
  1245.         set end [nextLineStart $start]
  1246.         set text [getText $start $end]
  1247.         set lab ""
  1248.         if {[regexp $bibTopPat2 $text mtch type citekey]} {
  1249.             if {[string tolower $type] != "string" || $BibmodeVars(markStrings)} { 
  1250.                 setNamedMark $citekey [lineStart [expr $start - 1]] $start $start
  1251.             }
  1252.         }
  1253.         set pos $end
  1254.     }
  1255. }
  1256.  
  1257. ###########################################################################
  1258. # Report the number of entries of each type
  1259. #
  1260. proc countEntries {} {
  1261.     global entryNames
  1262.      global bibTopPat bibTopPat1 bibTopPat2
  1263.     
  1264.     set pos 0
  1265.     set count 0
  1266.     catch {unset type}
  1267.     
  1268.     while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat $pos} res]} {
  1269.         incr count
  1270.         set start [lindex $res 0]
  1271.         set end [nextLineStart $start]
  1272.         set text [getText $start $end]
  1273.         set lab ""
  1274.         if {[regexp $bibTopPat $text mtch entryType]} {
  1275.             set entryType [string tolower $entryType]
  1276.             if {[catch {incr type($entryType)} num]} {
  1277.                 set type($entryType) 1
  1278.             }
  1279.         }
  1280.         set pos $end
  1281.     }
  1282.     new -n {*BibTeX Statistics*}
  1283.     newMode Bib
  1284.     foreach name [lsort [array names type]] {
  1285.         if {$type($name) > 0} {
  1286.             append lines [format "%4.0d  %s\n" $type($name) $name]
  1287.         }
  1288.     }
  1289.     append lines "----  -----------------\n"
  1290.     append lines [format "%4.0d  %s\n" $count "Total entries"]
  1291.     insertText $lines
  1292.     goto 0
  1293.     setWinInfo dirty 0
  1294.     catch {shrinkWindow 1}
  1295. }
  1296. #--------------------------------------------------------------------------
  1297. # command-double-clicking:
  1298. #--------------------------------------------------------------------------
  1299.  
  1300. ###########################################################################
  1301. # In Bib mode, Cmd-double-clicks resolve abbrevs and cross-refs
  1302. #
  1303. proc BibDblClick {from to} {
  1304.     global bibTopPat bibTopPat1 bibTopPat2
  1305.     
  1306.     set limits [getEntry $from]
  1307.     set top [lindex $limits 0]
  1308.     set bottom [lindex $limits 1]
  1309.  
  1310.     # Extend selection to largest string that could be an entry reference
  1311.     set text [string trim [eval getText [BibExtendClick $from $to $top $bottom]]]
  1312.     
  1313.     # Get the citekey of current entry, so we can avoid jumping to it    
  1314.     set citekey {}
  1315.     regexp $bibTopPat2 [getText $top $bottom] mtch type citekey ]
  1316.     set fldName [getFldName $from $top]
  1317.  
  1318.     if {[string length $text] == 0 || $text == $citekey || $fldName == $text || 
  1319.         ($fldName == "citekey" && [string tolower $type] != "string")} {
  1320.         message "Command-double-click on abbreviations and crossref arguments"
  1321.         return
  1322.     }
  1323.  
  1324.     # Jump to the mark for the specified citation, if a mark exists...
  1325.     # ...otherwise, do an ordinary search for the cite key
  1326.     pushMark    
  1327.     set searchPat "$bibTopPat\[     \]*[quoteExpr $text]\[     ,\}\)\]"
  1328.     if {![catch {search -f 1 -r 1 -i 1 -m 0 $searchPat 0} mtch]} {
  1329.         goto [lindex $mtch 0]
  1330.     } else {
  1331.         popMark
  1332.         select $from $to
  1333.         if {$fldName == "crossref"} {
  1334.             message "Cross-reference \"$text\" not found"
  1335.         } else {
  1336.             message "Command-double-click on abbreviations and crossref arguments"
  1337.         }
  1338.         return
  1339.     }
  1340.     message "Use Ctl-. to return to original position"
  1341.     return
  1342. }
  1343.  
  1344. # Extend the selection around the initial selection {$from,$to}
  1345. # Extension is restricted to the range {$top,$bottom} (the current entry)
  1346. proc BibExtendClick {from to top bottom} {
  1347.     if {$to == 0} { set to $from }
  1348.     set result [list $from $to]
  1349.     if {![catch {search -f 0 -r 1 -s -m 0 -l $top "\[,\{\]\"\'=" $from} mtch0]} {
  1350.         if {![catch {search -f 1 -r 1 -s -m 0 -l $bottom "\[,\}\]\"\'=" $to} mtch1]} {
  1351.             set from [lindex $mtch0 1]
  1352.             set to [lindex $mtch1 0]
  1353.             # Check for illegal chars embedded in the selection
  1354.             if {[regexp "\[\{\}\]=" [getText $from $to]] == 0} {
  1355.                 set result [list $from $to]
  1356.             }
  1357.         }
  1358.     }
  1359.     return $result
  1360. }
  1361.  
  1362.  
  1363. ###########################################################################
  1364.  
  1365. proc dummyBibTeX {} {
  1366.     global BibmodeVars TeXmodeVars
  1367. #     if {$BibmodeVars(convert8bitAscii2TeX) != $TeXmodeVars(convert8bitAscii2TeX)} {
  1368. #         set BibmodeVars(convert8bitAscii2TeX) $TeXmodeVars(convert8bitAscii2TeX)
  1369. #     }    
  1370. }
  1371.  
  1372. #
  1373.  
  1374. #===============================================================================
  1375. proc pcite {} {
  1376.     set words [getline "Citation keys" ""]
  1377.     if {![llength $words]} {error "No keys"}
  1378.     
  1379.     set pattern {@}
  1380.     foreach w $words {
  1381.         append pattern "(\[^@\]+$w)"
  1382.     }
  1383.     
  1384.     foreach entry [findEntries $pattern] {
  1385.         set res [getFields [car $entry]]
  1386.         set title [lindex [cadr $res] [lsearch [car $res] "title"]]
  1387.         set citekey [lindex [cadr $res] [lsearch [car $res] "citekey"]]
  1388.         set matches($title) $citekey
  1389.         set where($title) [car $entry]
  1390.     }
  1391.     if {![info exists matches]} {alertnote "No citations"; return}
  1392.     set title [listpick -p "Citation?" [lsort [array names matches]]]
  1393.     putScrap $matches($title)
  1394.     alertnote $matches($title)
  1395.     goto $where($title)
  1396. }
  1397.